<?php

namespace Kangcg\Application\Security;

/**
 * 证书加解密
 * Class SecurityCertificate
 * @package Kang\Libs\Helper\Security
 */
class Certificate implements SecurityInterface
{
    private $padding_size = null;

    /**
     * @param array $config
     * @param string key 秘钥
     * @param string public 公钥
     * @param string private 私钥
     * @param int padding 补位
     */
    public function __construct($config = [])
    {
        $this->_config['key'] = $config['key'];
        $this->_config['public'] = $config['public'];
        $this->_config['private'] = $config['private'];
        $this->_config['padding'] = $config['padding'] ?? OPENSSL_PKCS1_PADDING;
    }

    public function getConfig($key = null)
    {
        return $key === null ? $this->_config : ($this->_config[$key] ?? '');
    }

    public function getEncryption(): string
    {
        return 'certificate';
    }

    /**
     * @param array $config 参数配置
     * @param string key 秘钥
     * @param string public 公钥
     * @param string private 私钥
     * @param int padding 补位
     * @return Certificate
     */
    public static function getInstall($config = [])
    {
        if (self::$_install === null) {
            self::$_install = new self($config);
        }

        return self::$_install;
    }

    /**
     * @param array $config
     * @return $this
     */
    public function init(array $config)
    {
        $this->_config = $config;
        return $this;
    }

    /**
     * 加密操作
     * @param string $encrypted
     * @return bool|mixed
     */
    public function encode($encrypted, $isUsePublic = false)
    {
        $encrypted = is_array($encrypted) ? json_encode($encrypted, JSON_UNESCAPED_UNICODE) : $encrypted;
        return $isUsePublic ? $this->publicEncrypt($encrypted) :
            $this->privateEncrypt($encrypted);
    }

    /**
     * 解密操作
     * @param string $encrypted
     * @return bool
     */
    public function decode($encrypted, $isUsePublic = true)
    {
        $encrypted = base64_decode($encrypted);
        return $isUsePublic ? $this->publicDecrypt($encrypted) :
            $this->privateDecrypt($encrypted);
    }

    /**
     * 获取签名
     * @param string $str
     * @return string
     */
    public function sign(string $str)
    {
        $key = $this->getPrivateKey();
        openssl_sign($str, $signature, $key);
        openssl_free_key($key);
        $sign = base64_encode($signature);
        return $sign;
    }

    /**
     * 私钥加密
     * @param string $data
     * @return mixed
     */
    public function privateEncrypt($data)
    {
        try {
            $key = $this->getPrivateKey();
            $string = '';
            $data = str_split($data, $this->padding_size - 11);
            foreach ($data as $datum) {
                openssl_private_encrypt($datum, $encrypted, $key, $this->_config['padding']);
                $string .= $encrypted;
            }

            openssl_free_key($key);
            return base64_encode($string);
        } catch (\Exception $exception) {
            return $this->setError($exception->getMessage());
        }
    }

    /**
     * 私钥解密
     * @param string $data
     * @return bool
     */
    public function privateDecrypt($data)
    {
        try {
            $key = $this->getPrivateKey();
            $string = '';
            $data = str_split($data, $this->padding_size);
            foreach ($data as $datum) {
                openssl_private_decrypt($datum, $decrypted, $key, $this->_config['padding']);
                $string .= $decrypted;
            }

            openssl_free_key($key);
            return $string;
        } catch (\Exception $exception) {
            return $this->setError($exception->getMessage());
        }
    }

    /**
     * 公钥加密
     * @param string $data 数据
     * @return bool
     */
    public function publicEncrypt($data)
    {
        try {
            $key = $this->getPublicKey();
            $string = '';
            $data = str_split($data, $this->padding_size - 1);
            foreach ($data as $datum) {
                openssl_public_encrypt($datum, $encrypted, $key, $this->_config['padding']);
                $string .= $encrypted;
            }

            openssl_free_key($key);
            return base64_encode($string);
        } catch (\Exception $exception) {
            return $this->setError($exception->getMessage());
        }
    }

    /**
     * 公钥解密
     * @param string $data
     * @return bool
     */
    public function publicDecrypt($data)
    {
        try {
            $key = $this->getPublicKey();
            $string = '';
            $data = str_split($data, $this->padding_size);
            foreach ($data as $datum) {
                openssl_public_decrypt($datum, $decrypted, $key, $this->_config['padding']);
                $string .= $decrypted;
            }

            openssl_free_key($key);
            return $string;
        } catch (\Exception $exception) {
            return $this->setError($exception->getMessage());
        }
    }

    /**
     * 配置私钥
     * openssl_pkey_get_private这个函数可用来判断私钥是否是可用的，可用，返回资源
     * @return bool|resource
     */
    private function getPrivateKey()
    {
        $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" .
            wordwrap($this->_config['private'], 64, "\n", true) .
            "\n-----END RSA PRIVATE KEY-----";

        $privateKey = openssl_pkey_get_private($privateKey);
        if ($this->padding_size <= 30) {
            $detail = openssl_pkey_get_details($privateKey);
            $this->padding_size = $detail['bits'] / 8;
        }

        return $privateKey;
    }

    /**
     *配置公钥
     * @return resource
     */
    public function getPublicKey()
    {
        $publicKey = "-----BEGIN PUBLIC KEY-----\n" .
            wordwrap($this->_config['public'], 64, "\n", true) .
            "\n-----END PUBLIC KEY-----";

        $publicKey = openssl_pkey_get_public($publicKey);
        if ($this->padding_size <= 30) {
            $detail = openssl_pkey_get_details($publicKey);
            $this->padding_size = $detail['bits'] / 8;
        }

        return $publicKey;
    }

    private function setError($error)
    {
        $this->_error = $error;
        return false;
    }

    public function getError()
    {
        return $this->_error;
    }

    private $_config = [];
    private $_error = null;
    private static $_install = null;
}
